#include <iostream>
#include <sys/time.h>
#include <sys/select.h>
#include "Socket.hpp"

using namespace std;
static const uint16_t defaultport = 8888;
static const int fd_num_max = (sizeof(fd_set) * 8); // 监听文件描述符最大个数
int defaultfd = -1;
class SelectServer
{
public:
    SelectServer(uint16_t port = defaultport) : _port(port)
    {
        for (int i = 0; i < fd_num_max; i++)
        {
            fd_array[i] = defaultfd;
            std::cout << "fd_array[" << i << "]" << fd_array[i] << std::endl;
        }
    }
    bool Init()
    {
        _listensock.Socket();    // 创建套接字
        _listensock.Bind(_port); // 绑定套接字
        _listensock.Listen();
        return true;
    }
    void Accepter()
    {
        // 连接已就绪，开始处理
        std::string clientip;
        uint16_t clientport = 0;
        // accept
        int sock = _listensock.Accept(&clientip, &clientport);
        if (sock < 0)
            return;
        lg(Info, "accept success ,%s:%d", clientip.c_str(), clientport);

        // 把sock套接字添加进辅助数组，从1开始遍历辅助数组，因为0是监听套接字
        int pos = 1;
        for (; pos < fd_num_max; pos++)
        {
            // 被设置过
            if (fd_array[pos] != defaultfd)
                continue;
            else
                break; // fd_array[pos]等于defaultfd，说明该位置没有被设置文件描述符过，break，准备使用该位置记录accept的套接字
        }
        // 如果for循环走完了，走到最后一个位置，说明辅助数组全被设置过，位置占满了
        if (pos == fd_num_max)
        {
            // 位置占满了，关闭当前accept的套接字
            lg(Warning, "server is full,close %d now!", sock);
            close(sock);
        }
        else
        {
            // 新获取到的连接向数组空位置添加
            fd_array[pos] = sock;
        }
        cout << "执行到这里，接收连接逻辑完成,接收套接字为： " << sock << endl;
    }
    void Recver(int fd, int pos)
    {
        char buffer[1024];
        cout << "read开始阻塞等待，当前套接字为：" << fd << endl;
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "get a message:" << buffer << endl;
        }
        else if (n == 0) // 读到0，对方把文件描述符关了
        {
            lg(Info, "client quit, me too,close fd is: ", fd);
            // 服务端也要关闭该文件描述符
            close(fd);
            fd_array[pos] = defaultfd; // 从select读集合中移除该文件描述符
        }
        else
        {
            lg(Warning, "recv error: fd is: %d", fd);
            close(fd);
            fd_array[pos] = defaultfd;
        }
    }
    // 监听到一个新连接，处理
    void Dispatcher(fd_set &rfds)
    {
        // 已经就绪的位图在rfds里
        for (int i = 0; i < fd_num_max; i++)
        {
            int fd = fd_array[i]; // 得到指定文件描述符
            if (fd == defaultfd)
                continue;

            // 判断哪些文件描述符在集合当中
            // 如果当前文件描述符==当前监听的套接字且rfds集合里的该监听套接字文件描述符已被设置（如果在，说明事件就绪）
            if (FD_ISSET(fd, &rfds))
            {
                // 我是监听套接字并且我已经就绪
                if (fd == _listensock.Fd())
                {
                    Accepter(); // 来连接管理器
                }
                // fd != _listensock.Fd()，其他文件描述符就绪，其他文件描述符可以读
                else
                {
                    Recver(fd, i);
                }
            }
        }
    }
    void Start()
    {
        int listensock = _listensock.Fd();
        fd_array[0] = listensock; // 数组第一个位置设置为监听套接字
        for (;;)
        {

            // 将所有合法的文件描述符添加进rfds集合
            fd_set rfds;    // 栈上的变量
            FD_ZERO(&rfds); // 把位图清空
            int maxfd = fd_array[0];
            // 遍历辅助数组，利用辅助数组更新rfds集合
            for (int i = 0; i < fd_num_max; i++)
            {
                if (fd_array[i] == defaultfd)
                    continue; // 如果当前文件描述符是-1，说明当前位置没有被设置过
                // 如果fd_array[i]不是defaultfd，说明当前位置是被设置过的，有效的
                FD_SET(fd_array[i], &rfds); // 把文件描述符对应的编号写进位图

                // 求最大文件描述符
                if (maxfd < fd_array[i])
                {
                    maxfd = fd_array[i];
                    lg(Info, "max fd update,max fd is:%d", maxfd);
                }
            }
            cout << "遍历数组完成" << endl;

            // 这里不能使用accept，因为accept的本质就是检测listensocket，就是在等待文件描述符连接事件
            // （读事件）是否就绪
            // 如果调用accept就变成了等待一个连接，而不是等待多个连接
            // rfds这个栈上的变量经过select函数执行后，数据才被写入内核中
            struct timeval timeout = {5, 0}; // 重复周期性的设置timeout时间
            // rfds是输入输出型参数，为了保持监听的文件描述符编号不变，需要在每次循环时重新设置rfds
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
            cout << "已经执行完select函数" << endl;
            switch (n)
            {
            case 0:
                cout << "time out,timeout:" << timeout.tv_sec << "." << timeout.tv_usec << endl;
                break;
            case -1:
                cerr << "Select error" << endl;
                break;
            default:
                // 有事件就绪,如果不处理这份连接，底层会一直通知上层“已经获取到连接”
                cout << "get a new link" << endl;
                // 事件派发器
                Dispatcher(rfds); // 哪个文件描述符的连接（读）事件就绪信息保存在rfds变量里
                break;
            }
        }
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    Sock _listensock; // 定义一个Sock类型对象，这个类型已做好套接字的封装
    uint16_t _port;
    int fd_array[fd_num_max]; // 存放文件描述符的辅助数组
};